/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

/*! \file Интерпретация текста
 *
 *  Утилита интерпретации текста на любом языке, заданном грамматикой на языке SIMODO fuze.
 *
 *  Проект SIMODO.
*/

#include "simodo/interpret/SemanticModuleFactory_interface.h"

#include "simodo/inout/reporter/ConsoleReporter.h"
#include "simodo/inout/convert/functions.h"
#include "simodo/interpret/InterpretType.h"
#include "simodo/module/ModuleManagement.h"
#include "simodo/inout/token/RegularInputStreamSupplier.h"
#include "simodo/LibVersion.h"

#include <string>
#include <vector>
#include <functional>
#include <iostream>
#include <fstream>
#include <memory>
#include <algorithm>
#include <chrono>

#if __cplusplus >= __cpp_2017
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::filesystem::experimental;
#endif

#include <boost/dll/import.hpp>

namespace dll = boost::dll;

using namespace simodo;

static const std::string DIGITS = "0123456789";

int main(int argc, char *argv[])
{
    std::vector<std::string> arguments(argv + 1, argv + argc);

    std::string                 file_name           = "";
    std::vector<std::string>    interpret_places;
    std::vector<std::string>    hard_module_places;
    std::vector<std::string>    grammar_places;
    std::vector<std::string>    preload_module_names;
    bool                        need_time_intervals = false;
    bool	                    error               = false;
    bool	                    help                = false;
    bool	                    version             = false;
    bool                        need_silence        = false;
    std::string                 type_string         = "";
    std::string                 initial_contracts_file = module::INITIAL_CONTRACTS_FILE;
    bool                        debug_mode          = false;
    uint64_t                    timeout             = 0;
    bool                        resume              = true;
    bool                        need_full_debug_info= false;
    std::vector<interpret::BreakPoint> breakpoints;

    for(size_t i=0; i < arguments.size(); ++i)
    {
        const std::string & arg = arguments[i];

        if (arg[0] == '-')
        {
            if (arg == "--help" || arg == "-h")
                help = true;
            else if (arg == "--version" || arg == "-v")
                version = true;
            else if (arg == "--type" || arg == "-p")
            {
                if (i == arguments.size()-1 || !type_string.empty())
                    error = true;
                else
                    type_string = arguments[++i];
            }
            else if (arg == "--semantics-dir" || arg == "-s")
            {
                if (i == arguments.size()-1)
                    error = true;
                else
                    interpret_places.push_back(arguments[++i]);
            }
            else if (arg == "--hard-modules-dir" || arg == "-a")
            {
                if (i == arguments.size()-1)
                    error = true;
                else
                    hard_module_places.push_back(arguments[++i]);
            }
            else if (arg == "--preload-module" || arg == "-m")
            {
                if (i == arguments.size()-1)
                    error = true;
                else
                    preload_module_names.push_back(arguments[++i]);
            }
            else if (arg == "--grammars-dir" || arg == "-g")
            {
                if (i == arguments.size()-1)
                    error = true;
                else
                    grammar_places.push_back(arguments[++i]);
            }
            else if (arg == "--initial-contracts-file" || arg == "-c")
            {
                if (i == arguments.size()-1)
                    error = true;
                else
                    initial_contracts_file = arguments[++i];
            }
            else if (arg == "--time-intervals" || arg == "-t")
                need_time_intervals = true;
            else if (arg == "--silence" || arg == "-S")
                need_silence = true;
            else if (arg == "--stop") {
                resume = false;
                debug_mode = true;
                if (i != arguments.size()-1 && DIGITS.find(arguments[i+1][0]) != std::string::npos) {
                    ++i;
                    timeout = std::stoul(arguments[i]);
                }
            }
            else if (arg == "--debug" || arg == "-d") {
                debug_mode = true;
                if (i != arguments.size()-1 && DIGITS.find(arguments[i+1][0]) != std::string::npos) {
                    ++i;
                    timeout = std::stoul(arguments[i]);
                }
            }
            else if (arg == "--full-debug-info" || arg == "-f") {
                need_full_debug_info = true;
                debug_mode = true;
            }
            else if (arg == "--breakpoint" || arg == "-b") {
                debug_mode = true;
                resume = false;
                if (i == arguments.size()-1)
                    error = true;
                else {
                    ++i;
                    bool line_first = true;
                    for(const auto & c : arguments[i])
                        if (DIGITS.find(c) == std::string::npos) {
                            line_first = false;
                            break;
                        }
                    if (line_first)
                        breakpoints.push_back({file_name, inout::position_line_t(std::stoul(arguments[i]))});
                    else if (i == arguments.size()-1)
                        error = true;
                    else {
                        std::string uri = arguments[i];
                        ++i;

                        bool line_second = true;
                        for(const auto & c : arguments[i])
                            if (DIGITS.find(c) == std::string::npos) {
                                line_second = false;
                                break;
                            }
                        if (line_second)
                            breakpoints.push_back({uri, inout::position_line_t(std::stoul(arguments[i]))});
                        else
                            error = true;
                    }
                }
            }
            else
                error = true;
        }
        else if (file_name.empty())
            file_name = arg;
        else
            error = true;
    }

    interpret::InterpretType interpret_type = interpret::InterpretType::Preview;

    if (type_string == "analyze" || type_string == "a")
        interpret_type = interpret::InterpretType::Analyzer;
    else if (type_string == "preview" || type_string == "v")
        interpret_type = interpret::InterpretType::Preview;
    else if (!type_string.empty()) {
        std::cout << "Задан недопустимый тип интерпретации" << std::endl;
        error = true;
    }

    if (file_name.empty() && !version && !help)
        error = true;

    if (error) {
        std::cout << "Ошибка в параметрах запуска" << std::endl;
        help = true;
    }

    const std::string logo = "Утилита интерпретации. Проект SIMODO.";

    {
        using namespace std;

        if (help)
            cout	<< logo << endl
                    << "Формат запуска:" << endl
                    << "    simodo-interpret [<параметры>] <файл>" << endl
                    << "Параметры:" << endl
                    << "    -h | --help                       - отображение подсказки по запуску программы" << endl
                    << "    -v | --version                    - отображение версии программы" << endl
                    << "    -p | --type {a|v|analyze|preview} - тип интерпретации (по умолчанию: run)" << endl
                    << "    -s | --semantics-dir <путь>       - путь к интерпретаторам (по умолчанию: bin/semantics)" << endl
                    << "    -a | --hard-modules-dir <путь>    - путь к каталогу жёстких модулей (по умолчанию: bin/modules)" << endl
                    << "    -g | --grammars-dir <путь>        - путь к каталогу грамматик (по умолчанию: data/grammar)" << endl
                    << "    -c | --initial-contracts-file <путь> - путь к файлу обязательных контрактов" << endl
                    << "                                           (по умолчанию: initial-contracts.simodo-script)," << endl
                    << "                                           должен находиться в каталоге: data/grammar/contracts" << endl
                    << "    -m | --preload-module <имя>       - имя модуля для предварительно загрузки (можно указать несколько раз)" << endl
                    << "    -t | --time-intervals             - отображать интервалы времени разбора" << endl
                    << "    -d | --debug [<timeout>]          - режим отладки и timeout для остановки по времени (в секундах)" << endl
                    << "       | --stop [<timeout>]           - прекращать работу после остановки по истечению времени (в секундах)" << endl
                    << "    -f | --full-debug-info            - выводить полную информацию по всем рабочим нитям" << endl
                    << "    -b | --breakpoint [<file>] <line> - установить точку останова (можно задавать многократно)" << endl
                    << "    -S | --silence                    - не выводить диагностику утилиты" << endl
                    ;
    }

    if (error)
        return 1;

    if (version)
        std::cout << logo << std::endl
             << "Версия: " << lib_version().major() << "." << lib_version().minor() << std::endl;

    if (file_name.empty())
        return 0;

    if (interpret_places.empty())
        interpret_places.push_back("bin/semantics");

    if (hard_module_places.empty())
        hard_module_places.push_back("bin/modules");

    if (grammar_places.empty())
        grammar_places.push_back("data/grammar");

    if (!initial_contracts_file.empty()) {
        fs::path initial_contracts_path = fs::path(grammar_places[0])
                                / fs::path(module::INITIAL_CONTRACTS_DIR) 
                                / fs::path(module::INITIAL_CONTRACTS_FILE);
        initial_contracts_file = initial_contracts_path.string();
    }

    inout::ConsoleReporter            r;
    inout::RegularInputStreamSupplier file_supplier;
    interpret::SemanticDataCollector_null semantic_data;
    module::ModuleManagement          mm(   r, 
                                            file_supplier, 
                                            interpret_places,
                                            hard_module_places, 
                                            grammar_places, 
                                            interpret_type,
                                            semantic_data,
                                            std::cerr,
                                            initial_contracts_file,
                                            debug_mode,
                                            timeout,
                                            resume,
                                            need_full_debug_info,
                                            breakpoints
                                        );

    auto start_of_interpret = std::chrono::high_resolution_clock::now();

    int ret = mm.execute(file_name, preload_module_names) ? 0 : 1;

    if (ret == 0) {
        if (need_time_intervals) {
            auto elapsed = std::chrono::duration<float>(std::chrono::high_resolution_clock::now() - start_of_interpret);

            std::cout << "Интерпретация выполнена за " << elapsed.count() << " с" << std::endl;
        }
        else if (!need_silence)
            std::cout << "Интерпретация выполнена успешно" << std::endl;
    }
    else if (!need_silence)
        std::cout << "Интерпретация прервана" << std::endl;

    return ret;
}
